﻿using log4net;
using Newtonsoft.Json.Linq;
using per.esmool.utils;
using System;
using System.Collections.Generic;
using System.Linq;

namespace FlowSimulate {

    public class Rhetorician {

        private static readonly ILog LOG = LogManager.GetLogger(typeof(Rhetorician));

        readonly Dictionary<string, List<Description>> descriptions;
        readonly List<Description> commentDescriptions;

        public Rhetorician(string descriptionsFilePath) {
            var qs = LoadDescriptions(descriptionsFilePath);
            var comments = qs["comment"];
            qs.Remove("comment");
            this.descriptions = qs;
            this.commentDescriptions = PrepareCommentDescriptions(comments);
            LOG.Debug($"Descriptions load from {descriptionsFilePath}");
        }

        private static Dictionary<string, List<Description>> LoadDescriptions(string filepath) {
            var data = YamlKit.ParseFile<JArray>(filepath);
            data = JsonKit.Merge(data, "key", new() {
                MergeNullValueHandling = MergeNullValueHandling.Merge,
                MergeArrayHandling = MergeArrayHandling.Replace,
            });
            var qs = from item in data
                     where item is JObject
                     let json = item as JObject
                     let type = json.Value<string>("type")
                     let value = new Description(json)
                     select new { Type = type, Value = value };
            return qs
                .GroupBy(x => x.Type, x => x.Value)
                .ToDictionary(x => x.Key, x => x.ToList());
        }

        private static List<Description> PrepareCommentDescriptions(List<Description> comments) {
            comments.Sort((x, y) => Math.Sign(x.CommentDuration ?? 0 - y.CommentDuration ?? 0));
            var Ts = comments.Select(x => x.CommentDuration ?? 0).ToList();
            var maxT = Ts.Max();
            foreach (var comment in comments)
                comment.CommentDuration = (comment.CommentDuration ?? 0) / maxT;
            return comments;
        }

        public ResourceDispatcher ResourceDispatcher { get; set; }

        public AIInvoker AIInvoker { get; set; }

        public List<PersonatedTask> Personate(List<ArrangedTask> arrangedTasks) {
            var list = new List<PersonatedTask>();
            foreach (var task in arrangedTasks) {
                var personated = new PersonatedTask(task);
                personated.Initialize(this.descriptions, this.commentDescriptions, this.ResourceDispatcher);
                list.Add(personated);
            }
            return list;
        }
    }

    public class PersonatedTask : ArrangedTask {

        public PersonatedTask(ArrangedTask from) : base(from) { }

        public PersonatedTask(PersonatedTask from) : base(from) {
            this.Title = from.Title;
            this.Description = from.Description;
            this.Comments = from.Comments.Select(x => new Comment(x)).ToList();
        }

        public List<string> Resources { get; set; }

        public string Title { get; set; }
        public string Description { get; set; }
        public List<Comment> Comments { get; set; }

        public Suit Suit { get; set; }
        public Director Director { get; set; }
        public WorkDateKit DateKit { get; set; }
        public AIInvoker AIInvoker { get; set; }

        readonly RandKit rand = new();

        private string AssetInterpoletor(string expression) {
            if (expression == "OWNER")
                return this.Asset.Key;
            var list = this.Director.GetAssetByStageKey(expression);
            var asset = this.rand.Sample(list);
            return asset.Key;
        }

        public void Initialize(
            Dictionary<string, List<Description>> descriptions,
            List<Description> commentDescriptions,
            ResourceDispatcher resourceDispatcher
        ) {
            var formatter = new FormatKit() {
                RandomTextInterpoletor = this.rand.GetRandomTextInterpoletor(),
                AssetInterpoler = this.AssetInterpoletor
            };

            var module = this.Suit.GetModule(this.Module);
            var stage = this.Suit.GetStage(this.Stage);

            var moduleName = module.Value<string>("name");
            var moduleDescription = module.Value<string>("description");
            var stageName = stage.Value<string>("name");
            var taskName = $"{moduleName}{stageName}";

            string rhetorite(string type) {
                var text = this.CreateField(type, descriptions, formatter);
                var rhetorited = this.AIInvoker.Invoke(this.Stage, moduleName, stageName, taskName, moduleDescription) ?? string.Empty;
                return rhetorited;
            }
            var pmID = formatter.AssetInterpoler("{s:PM}");
            var ownerID = this.Asset.ID;

            this.Title = rhetorite("title");
            this.Description = rhetorite("description");
            this.Comments = this.CreateComments(commentDescriptions, formatter);
            this.Resources = resourceDispatcher[this.Module, this.Stage];
            this.OpenedBy = pmID;
            this.FinishedBy = ownerID;
            this.ClosedBy = pmID;
            this.LastEditedBy = pmID;
            this.LastEditDate = this.EndDate;
        }

        private string CreateField(string type, Dictionary<string, List<Description>> descriptions, FormatKit formatter) {
            var choices = descriptions[type];
            var description = this.rand.Sample(choices);
            var format = description.Format;
            var module = this.Suit.GetModule(this.Module);
            var moduleName = module.Value<string>("name");
            var moduleDescription = module.Value<string>("description");
            var text = formatter.Interpolate(format, moduleName, moduleDescription);
            return text;
        }

        private List<Comment> CreateComments(List<Description> commentDescriptions, FormatKit formatter) {
            var list = new List<Comment>();
            foreach (var description in commentDescriptions) {
                var module = this.Suit.GetModule(this.Module);
                var moduleName = module.Value<string>("name");
                var moduleDescription = module.Value<string>("description");
                var time = this.DateKit.AddWorkMinutes(this.StartDate, (int)(description.CommentDuration.Value * this.Cost * this.DateKit.DurtyTimeOfDay));
                var userID = formatter.Interpolate(description.CommentWriter);
                Comment comment = new() {
                    User = userID,
                    CreateTime = time,
                    Content = formatter.Interpolate(description.Format, moduleName, moduleDescription),
                    Type = description.Type == "normal" ? CommentType.Comment : CommentType.Action
                };
                list.Add(comment);
                switch (description.CommentType) {
                    case "approved":
                        this.AssignDate = time;
                        break;
                    case "finished":
                        this.FinishDate = time;
                        comment.Action = "finished";
                        break;
                    case "closed":
                        comment.Action = "closed";
                        break;
                    default:
                        break;
                }
            }
            return list;
        }

    }

    public class Description(JObject json) {

        public string Type { get; set; } = json.Value<string>("type");
        public string Format { get; set; } = json.Value<string>("format");
        public string Stage { get; set; } = json.Value<string>("stage");
        public string CommentType { get; set; } = json.Value<string>("commentType");
        public int CommentOrder { get; set; } = json.Value<int>("commentOrder");
        public bool CommentOnce { get; set; } = json.Value<bool>("commentOnce");
        public string CommentWriter { get; set; } = json.Value<string>("commentWriter");
        public float? CommentDuration { get; set; } = JsonKit.GetValue<float?>(json, "commentDuration", null);

        public int CommentWorkTimeOffset { get; private set; }

        public string Parse(FormatKit formatter, params string[] args) {
            return formatter.Interpolate(this.Format, args);
        }
    }

    public class Comment {

        public Comment() { }

        public Comment(Comment from) {
            this.User = from.User;
            this.CreateTime = from.CreateTime;
            this.Content = from.Content;
            this.Type = from.Type;
            this.Action = from.Action;
        }

        public string User { get; set; }
        public DateTime CreateTime { get; set; }
        public string Content { get; set; }
        public CommentType Type { get; set; }
        public string Action { get; set; }
    }

    public enum CommentType { Comment, Action }
}